#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include "vm.h"
#include "util.h"

#define MAX_LINE 80

#define MAX_OPCODE 4
#define MAX_INSTR 5

#define RCONV(s) (int8_t)strtol(s, NULL, 16)
#define VCONV(s) (int16_t)strtol(s, NULL, 16)
#define STRNCMP(a,b,sz) (!(strncmp(a, b, sz)))

int open_file(char* path, char* mode, FILE** fp) {
  *fp = fopen(path, mode);
  if (!*fp) {
    printf("Couldn't open file: %s\n", path);
    return 1;
  }
  return 0;
}

void split(char *_s, char* arr[]) {
  int l;
  int i=0;
  char* s = strndup(_s, MAX_LINE);
  char* token;

  while (token = strsep(&s, " ")) {
    arr[i] = strndup(token, MAX_OPCODE);
    i++;
  } 
  
  free(s);
}

#define OPSWITCH(a) if (STRNCMP(a, op, MAX_OPCODE))
#define OPCASE(a) else OPSWITCH(a)
int encode(char* op) {
  OPSWITCH("halt") { return HALT; }
  OPCASE("add")    { return ADD;  }
  OPCASE("sub")    { return SUB;  }
  OPCASE("mlt")    { return MLT;  }
  OPCASE("div")    { return DIV;  }
  OPCASE("ldi")    { return LDI;  }
  OPCASE("ldr")    { return LDR;  }
  OPCASE("ldm")    { return LDM;  }
  OPCASE("st16")   { return ST16; }
  OPCASE("st8")    { return ST8;  }
  OPCASE("jmp")    { return JMP;  }
  OPCASE("jmps")   { return JMPS; }
  OPCASE("dbg")    { return DBG;  }
  OPCASE("and")    { return AND;  }
  OPCASE("or")     { return OR;   }
  OPCASE("xor")    { return XOR;  }
  OPCASE("not")    { return NOT;  }
  OPCASE("rst")    { return RST;  }
  OPCASE("init")   { return INIT; }
  OPCASE("loop")   { return LOOP; }
  OPCASE("crr")    { return CRR;  }
  OPCASE("cri")    { return CRI;  }
  OPCASE("crm")    { return CRM;  }
  OPCASE("jgt")    { return JGT;  }
  OPCASE("jlt")    { return JLT;  }
  OPCASE("jeq")    { return JEQ;  }
  OPCASE("jgts")   { return JGTS; }
  OPCASE("jlts")   { return JLTS; }
  OPCASE("jeqs")   { return JEQS; }
  OPCASE("jge")    { return JGE;  }
  OPCASE("jle")    { return JLE;  }
  OPCASE("jges")   { return JGES; }
  OPCASE("jles")   { return JLES; }
  OPCASE("mod")    { return MOD;  }
  OPCASE("shl")    { return SHL;  }
  OPCASE("shr")    { return SHR;  }
  OPCASE("shli")   { return SHLI; }
  OPCASE("shri")   { return SHRI; }
  OPCASE("int")    { return INT;  }
  OPCASE("ldp")    { return LDP;  }
  OPCASE("inc")    { return INC;  }
  OPCASE("dec")    { return DEC;  }
  return -1;
}

void dumpInstr(instruction* instr) {
  int i;
  printf("Assembled instruction: ");
  printf("|%2x", instr->opcode);
  printf("|%3x", instr->reg1);
  printf("|%3x", instr->reg2);
  printf("|%3x", instr->reg3);
  printf("|%5x", instr->val);
  putc('\n', stdout);
}

#define pack(arr, ...) _pack(arr, __VA_ARGS__, -1)
void _pack(int16_t arr[], int16_t v, ...) {
  int val; int c=0;
  va_list args;

  val = v;
  va_start(args, v);
  while (val != -1) {
    arr[c] = val;
    val = (int16_t)va_arg(args, int);
    c++;
  }
  va_end(args);
}  

instruction* build(int opcode, int16_t reg1, int16_t reg2, int16_t reg3, int16_t val) {
  instruction* i = malloc(sizeof(*i));
  memset(i, 0, INSTR_BYTES);
  i->opcode = opcode;
  i->reg1   = reg1;
  i->reg2   = reg2;
  i->reg3   = reg3;
  i->val    = val;
  return i;
}

int isValid(char* line, size_t len) {
  if (!len)    { return 1; }
  if (len < 3) { return 1; }
  if (line[0] == '/' && line[1] == '/') { return 1; }
  char* c = strchr(line, '/');
  if (c && (*(c+1)) == '/') { *c = '\0'; }
  return 0;
}

instruction* encode_instr(char* args[]) {
  instruction* instr;
  int16_t opcode = encode(args[0]);
  switch (opcode) {
  case HALT:
  case RST:
  case INIT:
    instr = build(opcode, 0x0,            0x0,            0x0,            0x0);
    break;
  case ADD:
  case SUB:
  case MLT:
  case DIV:
  case AND:
  case OR:
  case XOR:
  case MOD:
  case SHL:
  case SHR:
    instr = build(opcode, RCONV(args[1]), RCONV(args[2]), RCONV(args[3]), 0x0);
    break;
  case NOT:
  case INC:
  case DEC:
    instr = build(opcode, RCONV(args[1]), 0x0,            0x0,            0x0);
    break;
  case LDI:
  case LDM:
  case ST16:
  case ST8:
  case LOOP:
  case CRI:
  case CRM:
  case SHLI:
  case SHRI:
    instr = build(opcode, RCONV(args[1]), 0x0,            0x0,            VCONV(args[2]));
    break;
  case LDR:
  case CRR:
  case LDP:  
    instr = build(opcode, RCONV(args[1]), RCONV(args[2]), 0x0,            0x0);
    break;
  case JMP:
  case JMPS:
  case JGT:
  case JLT:
  case JEQ:
  case JGTS:
  case JLTS:
  case JEQS:
  case JGE:
  case JLE:
  case JGES:
  case JLES:
  case DBG:
  case INT:
    instr = build(opcode, 0x0,            0x0,            0x0,            VCONV(args[1]));
    break;
  }
  return instr;
}

int main(int argc, char* argv[]) {
  FILE* infp; FILE* outfp;
  char* args[4];
  instruction* instr;
  int i;
  char* line = NULL;
  size_t len = 0;
  
  if (argc < 2) {
    printf("Syntax: %s <INPUT> <BINARY OUT>\n", argv[0]);
    return 1;
  }

  if (open_file(argv[1], "r", &infp) ) { return 1; }
  if (open_file(argv[2], "w", &outfp)) { return 1; }

  while (getline(&line, &len, infp) > 0) {
    if (!(isValid(line,len))) {
      rstrip(line, len);
      split(line, args);
      instr = encode_instr(args);
      if (!instr) {
        printf("Error at line: %s\n", line);
        return 1;
      }
      dumpInstr(instr);
      if (fwrite(instr, INSTR_BYTES, 1, outfp) == -1) {
        printf("Couldn't write to output file\n");
        return 1;
      }
    }
  }

  fclose(infp);
  fclose(outfp);

  return 0;
}
